#ifndef JZ_JSON_STREAM_H_
#define JZ_JSON_STREAM_H_

#include <assert.h>
#include <QJsonObject>
#include <QJsonArray>
#include <QFile>
#include <QJsonDocument>
#include "JZException.h"

class JsonRef
{
public:
    QJsonValue* value;
    QJsonObject* object;
    QString key;
};

class JsonStream
{
public:
    JsonStream();
    ~JsonStream();

    JsonStream &operator[](const QString& member);
    void close();

    template<class T>
    void setValue(const QString& name, const T& t)
    {
        (*this)[name] << t;
    }

    template<class T>
    T getValue(const QString& name, const T& def)
    {
        if (!value.isObject())
        {
            throw JZException("type not object");
        }

        auto obj = value.toObject();
        if (obj.contains(name))
        {
            T t;
            (*this)[name] >> t;
            return t;
        }
        else
        {
            return def;
        }
    }

    template<class T>
    void setOptional(const QString& name, const std::optional<T>& t)
    {
        if(t.has_value())
            (*this)[name] << t.value();
    }

    template<class T>
    const std::optional<T> getOptional(const QString& name)
    {
        if (!value.isObject())
        {
            throw JZException("type not object");
        }

        auto obj = value.toObject();
        if (obj.contains(name))
        {
            T t;
            (*this)[name] >> t;
            return t;
        }
        else
        {
            return std::optional<T>();
        }
    }

    bool read;
    JsonStream* parent;
    QJsonValue value;

protected:
    Q_DISABLE_COPY(JsonStream);

    QMap<QString,JsonStream*> m_subStream;
};

void operator<<(JsonStream& s, bool t);
void operator>>(JsonStream& s, bool& t);

void operator<<(JsonStream& s, int16_t t);
void operator>>(JsonStream& s, int16_t& t);

void operator<<(JsonStream& s, uint16_t t);
void operator>>(JsonStream& s, uint16_t& t);

void operator<<(JsonStream& s, int t);
void operator>>(JsonStream& s, int& t);

void operator<<(JsonStream& s, uint32_t t);
void operator>>(JsonStream& s, uint32_t& t);

void operator<<(JsonStream& s, int64_t t);
void operator>>(JsonStream& s, int64_t& t);

void operator<<(JsonStream& s, uint64_t t);
void operator>>(JsonStream& s, uint64_t& t);

void operator<<(JsonStream& s, float t);
void operator>>(JsonStream& s, float& t);

void operator<<(JsonStream& s, double t);
void operator>>(JsonStream& s, double& t);

void operator<<(JsonStream& s, const QString& t);
void operator>>(JsonStream& s, QString& t);

//QVector
template<class T>
void operator<<(JsonStream& s, const QVector<T>& vec)
{
    QJsonArray list;

    for (int i = 0; i < (int)vec.size(); i++)
    {
        JsonStream sub;
        sub.read = false;
        sub << vec[i];
        sub.close();

        list.append(sub.value);
    }

    s.value = list;
}

template<class T>
void operator>>(JsonStream& s, QVector<T>& vec)
{
    if (!s.value.isArray())
    {
        throw JZException("type not array");
    }

    QJsonArray array = s.value.toArray();
    vec.clear();
    for (int i = 0; i < array.size(); i++)
    {
        JsonStream sub;
        sub.value = array[i];

        T t;
        sub >> t;
        sub.close();
        vec.push_back(t);
    }
}

//QMap
template<class T>
void operator<<(JsonStream& s, const QMap<QString, T>& ptr)
{
    QJsonObject obj;

    auto it = ptr.begin();
    while (it != ptr.end())
    {
        JsonStream sub;
        sub.read = false;
        sub << ptr->second;
        sub.close();
        
        obj[it.key()] = sub.value;
        it++;
    }

    s.value = obj;
}

template<class T>
void operator>>(JsonStream& s, QMap<QString,T>& map)
{
    if (!s.value.isObject())
    {
        throw JZException("type not object");
    }
    map.clear();

    QJsonObject obj = s.value.toObject();
    auto it = obj.begin();
    while (it != obj.end())
    {
        JsonStream sub;
        sub.value = it.value();

        T t;
        sub >> t;
        sub.close();
        map[it.key()] = t;

        it++;
    }
}


//QSet
template<class T>
void operator<<(JsonStream& s, const QSet<T>& vec)
{
    QJsonArray list;

    auto it = vec.begin();
    while(it != vec.end())
    {
        JsonStream sub;
        sub.read = false;
        sub << it.value();
        sub.close();

        list.append(sub.value);
        it++;
    }

    s.value = list;
}

template<class T>
void operator>>(JsonStream& s, QSet<T>& vec)
{
    if (!s.value.isArray())
    {
        throw JZException("type not array");
    }

    QJsonArray array = s.value.toArray();
    vec.clear();
    for (int i = 0; i < array.size(); i++)
    {
        JsonStream sub;
        sub.value = array[i];

        T t;
        sub >> t;
        sub.close();
        vec.insert(t);
    }
}

template<class T>
QJsonValue saveToJson(const T& t)
{
    JsonStream s;
    s.read = false;
    s << t;
    s.close();

    return s.value;
}

template<class T>
bool loadFromJson(const QJsonValue& obj, T& t, QString* error = nullptr)
{
    try
    {
        JsonStream s;
        s.value = obj;
        s >> t;
        s.close();
        return true;
    }
    catch (const JZException& e)
    {
        if(error)
            *error = e.error();
        return false;
    }
}

template<class T>
QByteArray saveToJsonBuffer(const T& t)
{
    QJsonValue obj = saveToJson(t);
    return QJsonDocument(obj).toJson();
}

template<class T>
bool loadFromJsonBuffer(const QByteArray& buffer, T& t, QString* error)
{
    QJsonParseError parse_error;
    QJsonDocument doc = QJsonDocument::fromJson(buffer, &parse_error);
    if (parse_error.error != QJsonParseError::NoError)
    {
        if (error)
            error = "parse json failed";
        return false;
    }

    QJsonValue obj = doc.object();
    return loadFromJson(obj, t, error);
}

template<class T>
bool saveToJsonFile(const QString& path, const T& t)
{
    QFile file(path);
    if (!file.open(QFile::WriteOnly | QFile::Text))
    {
        return false;
    }

    QByteArray buffer = saveToJsonBuffer(t);
    file.write(buffer);
    file.close();
    return true;
}

template<class T>
bool loadFromJsonFile(const QString& path, T& t, QString *error)
{
    QFile file(path);
    if(!file.open(QFile::ReadOnly | QFile::Text))
    {
        if(error)
            error = "open file failed";
        return false;
    }

    QByteArray buffer = file.readAll();
    return loadFromJsonBuffer(buffer, t, error);
}

#endif